package com.vercer.engine.proxy;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.URL;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;

import com.google.appengine.api.urlfetch.FetchOptions;
import com.google.appengine.api.urlfetch.HTTPHeader;
import com.google.appengine.api.urlfetch.HTTPMethod;
import com.google.appengine.api.urlfetch.HTTPRequest;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;
import com.google.apphosting.api.ApiProxy.ApiConfig;
import com.google.apphosting.api.ApiProxy.ApiProxyException;
import com.google.apphosting.api.ApiProxy.Delegate;
import com.google.apphosting.api.ApiProxy.Environment;

public class RemoteDatastoreProxyDelegate extends DelegatingApiProxy
{
	private URLFetchService urlFetchService;
	private String remotePath;
	private static final Logger logger = Logger.getLogger(RemoteDatastoreProxyDelegate.class.getName());

	public RemoteDatastoreProxyDelegate(Delegate<Environment> delegate, String remotePath)
	{
		super(delegate);
		this.remotePath = remotePath;
		urlFetchService = URLFetchServiceFactory.getURLFetchService();
	}
	
	@Override
	public Future<byte[]> makeAsyncCall(Environment arg0, String arg1, String arg2, byte[] arg3, ApiConfig arg4)
	{
		if (shouldHandleCall(arg1))
		{
			logger.info("Diverting datastore request to " + getRemotePath());
			HTTPRequest request;
			try
			{
				request = createRequest(false, arg1, arg2, arg3);
			}
			catch (IOException e)
			{
				throw new IllegalArgumentException("Could not serialize request", e);
			}
			
			final Future<HTTPResponse> futureResponse = urlFetchService.fetchAsync(request);
			
			return new Future<byte[]>()
			{
				@Override
				public boolean cancel(boolean mayInterruptIfRunning)
				{
					return futureResponse.cancel(mayInterruptIfRunning);
				}
	
				@Override
				public byte[] get() throws InterruptedException, ExecutionException
				{
					return responseToBytes(futureResponse.get());
				}
	
				@Override
				public byte[] get(long timeout, TimeUnit unit) throws InterruptedException,
						ExecutionException, TimeoutException
				{
					return responseToBytes(futureResponse.get(timeout, unit));
				}
	
				@Override
				public boolean isCancelled()
				{
					return futureResponse.isCancelled();
				}
	
				@Override
				public boolean isDone()
				{
					return futureResponse.isDone();
				}
			};
		}
		else
		{
			return getDelegate().makeAsyncCall(arg0, arg1, arg2, arg3, arg4);
		}
	}

	protected boolean shouldHandleCall(String type)
	{
		return type.equals("datastore_v3");
	}
	
	protected byte[] responseToBytes(HTTPResponse httpResponse)
	{
		if (httpResponse.getResponseCode() < 200 || httpResponse.getResponseCode() > 300)
		{
			throw new IllegalArgumentException("Received response " + new String(httpResponse.getContent()));
		}
		return httpResponse.getContent();
	}

	protected HTTPRequest createRequest(boolean sync, String arg1, String arg2, byte[] arg3) throws IOException
	{
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream(baos);

		oos.writeUTF(arg1);
		oos.writeUTF(arg2);
		oos.writeObject(arg3);
		
		HTTPRequest request = new HTTPRequest(
				new URL(getRemotePath()), 
					HTTPMethod.POST, 
					FetchOptions.Builder.withDeadline(20));
		request.setPayload(baos.toByteArray());
		request.setHeader(new HTTPHeader("Content-Type", "application/octet-stream"));
		return request;
	}

	protected String getRemotePath()
	{
		return remotePath;
	}

	@Override
	public byte[] makeSyncCall(Environment arg0, String arg1, String arg2, byte[] arg3)
			throws ApiProxyException
	{
		if (shouldHandleCall(arg1))
		{
			try
			{
				HTTPRequest request = createRequest(true, arg1, arg2, arg3);
				HTTPResponse response = urlFetchService.fetch(request);
				return responseToBytes(response);
			}
			catch (IOException e)
			{
				throw new IllegalStateException("Problem calling remote service", e);
			}
		}
		else
		{
			return getDelegate().makeSyncCall(arg0, arg1, arg2, arg3);
		}
	}
}
